#pragma once

#include <stdexcept>
#include <sstream>

#include "Nodes/Defs.h"
#include "Utils/Compare.h"

//////// Binary node base class
class BinaryNode;
class BinaryNodeOr;
class BinaryNodeAnd;
class BinaryNodeNot;
class BinaryNodeAtom;

template<>
struct NodeTraits<BinaryNode>
{
    typedef BinaryNodeOr NodeOr;
    typedef BinaryNodeAnd NodeAnd;
    typedef BinaryNodeNot NodeNot;
    typedef BinaryNodeAtom NodeAtom;
};

class BinaryNode
{
	static const size_t kLeftNodeIndex = 0;
	static const size_t kRightNodeIndex = 1;
	static const size_t kMaxChildnrenCount = 2;

	const NodeType m_nodeType;
    const size_t m_childrenCount;
    BinaryNode * m_children[kMaxChildnrenCount];

private:
    BinaryNode(const BinaryNode &);
    BinaryNode& operator=(const BinaryNode &);

protected:
    BinaryNode(const NodeType nodeType, const size_t childrenCount, BinaryNode * left, BinaryNode * right)
        : m_nodeType(nodeType)
        , m_childrenCount(childrenCount)
    {
    	m_children[kLeftNodeIndex] = left;
    	m_children[kRightNodeIndex] = right;
    }

public:    
    virtual ~BinaryNode()
    {
    	delete m_children[kLeftNodeIndex];
    	delete m_children[kRightNodeIndex];
    }

    static inline std::string getNodeKind()
    {
        return "BinaryNode";
    }
    
    inline NodeType getNodeType() const
    {
        return m_nodeType;
    }
    
    inline size_t getChildrenCount() const
    {
        return m_childrenCount;
    }
    
    inline BinaryNode * getChild(const size_t idx) const
    {
    	// Validate parameter
    	if(idx >= getChildrenCount())
    		throw std::runtime_error("BinaryNode::getChild() - incorrect index");

    	return m_children[idx];
    }

    inline void setChild(size_t idx, BinaryNode * pNode)
    {
        if(idx >= getChildrenCount())
        	throw std::runtime_error("BinaryNode::setChild() - incorrect index");
        
        m_children[idx]=pNode;
    }

    virtual void accept(NodeVisitor<BinaryNode> * pVisitor) = 0;

    virtual bool compare(BinaryNode * pNode) const
    {
        return compareNodes(this, pNode);
    }

    virtual BinaryNode * clone() const = 0;
};

//////// Binary Or
class BinaryNodeOr : public BinaryNode
{
public:
    BinaryNodeOr(BinaryNode * pLeft, BinaryNode * pRight)
        : BinaryNode(NT_OR, 2, pLeft, pRight)
    {
    }

    virtual void accept(NodeVisitor<BinaryNode> * pVisitor)
    {
        pVisitor->visitOr(this);
    }

    virtual BinaryNode * clone() const
    {
		return new BinaryNodeOr(getChild(0)->clone(), getChild(1)->clone());
    }
};


//////// Binary And
class BinaryNodeAnd : public BinaryNode
{
public:
    BinaryNodeAnd(BinaryNode * pLeft, BinaryNode * pRight)
        : BinaryNode(NT_AND, 2, pLeft, pRight)
    {
    }

    virtual void accept(NodeVisitor<BinaryNode> * pVisitor)
    {
        pVisitor->visitAnd(this);
    }

    virtual BinaryNode * clone() const
    {
		return new BinaryNodeAnd(getChild(0)->clone(), getChild(1)->clone());
    }
};

//////// Binary Not
class BinaryNodeNot : public BinaryNode
{
public:
    BinaryNodeNot(BinaryNode * pArg)
        : BinaryNode(NT_NOT, 1, pArg, NULL)
    {
    }

    virtual void accept(NodeVisitor<BinaryNode> * pVisitor)
    {
        pVisitor->visitNot(this);
    }

    virtual BinaryNode * clone() const
    {
		return new BinaryNodeNot(getChild(0)->clone());
    }
};

//////// Binary Atom
class BinaryNodeAtom : public BinaryNode
{
    std::string m_text;
    
public:
    BinaryNodeAtom(const std::string & sText)
        : BinaryNode(NT_ATOM, 0, NULL, NULL)
        , m_text(sText)
    {
    }
    inline std::string getAtomText() const
    {
        return m_text;
    }

    virtual bool compare(BinaryNode * pNode) const
    {
        if(NT_ATOM != pNode->getNodeType())
            return false;
        
        return (m_text == static_cast<BinaryNodeAtom*>(pNode)->getAtomText());
    }

    virtual void accept(NodeVisitor<BinaryNode> * pVisitor)
    {
        pVisitor->visitAtom(this);
    }

    virtual BinaryNode * clone() const
    {
		return new BinaryNodeAtom(m_text);
    }
};


////////// Factory


template<>
inline BinaryNodeOr * NodeFactory<BinaryNode>::createNodeOr(BinaryNode * leftNode, BinaryNode * rightNode) const
{
    return new BinaryNodeOr(leftNode, rightNode);
}
    
template<>
inline BinaryNodeAnd * NodeFactory<BinaryNode>::createNodeAnd(BinaryNode * leftNode, BinaryNode * rightNode) const
{
    return new BinaryNodeAnd(leftNode, rightNode);
}
    
template<>
inline BinaryNodeNot * NodeFactory<BinaryNode>::createNodeNot(BinaryNode * node) const
{
    return new BinaryNodeNot(node);
}
    
template<>
inline BinaryNodeAtom * NodeFactory<BinaryNode>::createNodeAtom(const std::string & text) const
{
    return new BinaryNodeAtom(text);
}
    
template<>
inline std::string NodeFactory<BinaryNode>::getFactoryName() const
{
    return std::string("BinaryNodeFactory");
}



